<?php

class UserService extends AbstractService
{

    /**
     * 用户信息数据中，资源类信息，类型名称对应表
     *
     * @see GameConst::USER_INFO_RESOURCE_NAME_MAP
     * @var array
     */
    private $infoResourceNameMap;

    /**
     * 用户信息数据中，可恢复类资源类信息，可能配置项
     *
     * @see GameConst::USER_INFO_REFRESH_RESOURCE_NAME_MAP
     * @var array
     */
    private $infoRefreshResourcePossibleConfigKeys;

    /**
     * 用户信息数据中，资源类信息，可能配置项
     *
     * @see GameConst::USER_INFO_RESOURCE_KEY_MAP
     * @var array
     */
    private $infoResourcePossibleConfigKeys;

    /**
     * 用户信息数据中，不可超过上限的资源，如果超过，则废弃掉超过部分，并不需要报错
     *
     * @see GameConst::USER_INFO_RESOURCE_NOT_ALLOW_EXCEED
     * @var array
     */
    private $infoResourceNotAllowExceedKeys;

    /**
     * @var UserService
     */
    private static $instance;

    /**
     * Get instance of UserService.
     *
     * @return UserService
     */
    public static function get()
    {
        if (!self::$instance) {
            self::$instance = new UserService();
        }
        return self::$instance;
    }

    /**
     * Construction.
     */
    protected function __construct()
    {
        parent::__construct();

        $this->infoResourceNameMap = json_decode(GameConst::USER_INFO_RESOURCE_NAME_MAP, true);
        $this->infoResourcePossibleConfigKeys = json_decode(GameConst::USER_INFO_RESOURCE_KEY_MAP, true);
        $this->infoRefreshResourcePossibleConfigKeys = json_decode(GameConst::USER_INFO_REFRESH_RESOURCE_NAME_MAP, true);
        $this->infoResourceNotAllowExceedKeys = json_decode(GameConst::USER_INFO_RESOURCE_NOT_ALLOW_EXCEED, true);
    }

    /**
     * 处理创建时的逻辑
     *
     * @param int $userId
     * @param int $platformId
     * @return void
     */
    public function handleCreate($userId, $platformId = null)
    {

    }

    /**
     * 处理登录时的逻辑
     * 1.离线能量计算
     *
     * @param int $userId
     * @param ModuleProfileVo $profile
     * @return void
     */
    public function handleLogin($userId, $profile = null)
    {

    }

    /**
     * 提供Service层的所有数据
     * @param int $userId
     * @return array(ModuleProfileVo, ModuleProfileInfoVoList)
     * @throws SzException
     */
    public function info($userId)
    {
        $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);

        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);

        // 修复数据
        $profileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_CHANGE_INVITE_COUNT, false);
        if (!$profileInfo) {
            $inviteProfileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_INVITE_COUNT, false);
            /**
             * @var ModuleProfileInfoVoList $profileInfoList
             * @var ModuleProfileInfoVo $inviteProfileInfo
             */
            if ($inviteProfileInfo) {
                $inviteCount = $inviteProfileInfo->getRawInfo();
                if ($inviteCount) {
                    foreach ($inviteCount as &$v) {
                        switch ($v) {
                            case 10:
                                $v = 3;
                                break;
                            case 20:
                                $v = 6;
                                break;
                            case 30:
                                $v = 9;
                                break;
                            case 40:
                                $v = 12;
                                break;
                        }
                    }
                }
                $inviteProfileInfo->setInfo($inviteCount);
                $profileInfoList->updateElement(GameConst::USER_INFO_TYPE_INVITE_COUNT, $inviteProfileInfo);
            }

            $reqTime = GameService::get()->getReqTime();
            $profileInfo = new ModuleProfileInfoVo(
                $userId,
                GameConst::USER_INFO_TYPE_CHANGE_INVITE_COUNT,
                1,
                $reqTime,
                true
            );
            $profileInfoList->addElement($profileInfo);
            $profileInfoList->setElement(GameConst::USER_INFO_TYPE_CHANGE_INVITE_COUNT, $profileInfo);


            SzPersister::get()->setVoList($profileInfoList);
        }

        $reqTime = GameService::get()->getReqTime();
         /**
         * 获取玩家商品数据
         * @var ModuleItemVoList $itemList
         */
        $itemList = SzPersister::get()->getVoList($userId, ModuleItemConst::ORM_NAME_ITEM);
        /**
         * @var ModuleProfileInfoVo $headFrameInfo
         */
        $headFrameInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_HEAD_FRAME, false);
        $canDel = false;
        if ($headFrameInfo) {
            $headFrame = $headFrameInfo->getRawInfo();
            $num = 0;
            if ($headFrame) {
                $constArr = array(47011020,47011019,47011018);
                if (in_array($headFrame,$constArr)){
                    $itemData = $itemList->getElementsByItemDefId($headFrame);
                    if ($itemData){
                        // 获取道具的拥有数量
                        $num = $itemList->getItemDefCount($headFrame);
                   }
                    /**
                     * @var GuildCompetitionMemberVo $guildCompetitionMemberData
                     */
                    //获取玩家社团竞赛个人数据
                    $guildCompetitionMemberData =  SzPersister::get()->getVo($userId, GameConst::ORM_NAME_UNION_COMPETITION_MEMBER);
                    if ($guildCompetitionMemberData){
                        if ($guildCompetitionMemberData->getEndTime() < $reqTime){
                            $canDel = true;
                        }
                        else{
                            //带错误的头像框
                            /**
                             * @var ModuleProfileInfoVo $guildInfo
                             */
                            $guildInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_GUILD_ID, false);
                            if ($guildInfo){
                                $guildId = $guildInfo->getRawInfo();
                                //获取工会的公共数据
                                /**
                                 * @var GuildCompetitionVo $guildCompetitionData
                                 */
                                $guildCompetitionData = SzPersister::get()->getVo($guildId, GameConst::ORM_NAME_UNION_COMPETITION_COMMON_DATA);
                                if ($guildCompetitionData){
                                    if ($constArr[$guildCompetitionData->getQuality() - 1] != $headFrame){
                                        $canDel = true;
                                    }
                                }
                                else{
                                    $canDel = true;
                                }
                            }
                            else{
                                $canDel = true;
                            }
                        }
                    }
                    else{
                        $canDel = true;
                    }
                }

                if ($canDel && $headFrame){
                    $headFrameInfo->setTime($reqTime);
                    $headFrameInfo->setInfo(0);
                    $profileInfoList->updateElement(GameConst::USER_INFO_TYPE_HEAD_FRAME, $headFrameInfo);

                    if ($num){
                        GameService::get()->handleEntity($userId, '7401'.$headFrame, 1, true, true);
                        CommonFunctionService::get()->reportResourceUseDetails(99999976,$userId,0,'7401'.$headFrame.':1|','init');//消耗资源上报
                    }

                    //获取玩家头像框数据
                    /**
                     * @var $userDressList UserDressVoList
                     * @var $userDressData UserDressVo
                     */
                    $userDressList = SzPersister::get()->getVoList($userId, GameConst::ORM_NAME_USER_DRESS);
                    $userDressData = $userDressList->getElement(GameConst::ITEM_SPECIAL_SMALL_TYPE['Head_frame'],false);
                    if ($userDressData){
                        $headFrameLibrary = json_decode($userDressData->getStock());
                        $headFrameArr = explode(',',$headFrameLibrary);
                        $itemStr = '';
                        if (in_array($headFrame,$headFrameArr)){
                            foreach ($headFrameArr as $key1=>$data){
                                if ($data == $headFrame){
                                    unset($headFrameArr[$key1]);
                                    break;
                                }
                            }

                            if (empty($headFrameArr)){
                                $userDressData->setStock($itemStr);
                            }
                            else{
                                $itemStr = implode(',',$headFrameArr);
                                $userDressData->setStock($itemStr);
                            }
                            $userDressList->updateElement(GameConst::ITEM_SPECIAL_SMALL_TYPE['Head_frame'], $userDressData);
                            SzPersister::get()->setVoList($userDressList);
                        }
                    }

                    SzPersister::get()->setVoList($profileInfoList);
                }
            }
        }

        return array(
            $profile,
            $profileInfoList
        );
    }

    /**
     * 初始化用户数据
     *
     * @param int $userId
     * @param string $platformId
     * @param string $platform
     * @return array
     * <pre>
     * array($profile, $profileInfos)
     * </pre>
     */
    public function init($userId, $platformId, $platform)
    {
        $reqTime = GameService::get()->getReqTime();
        $ip = SzUtility::getClientHost();
        $appConfigs = SzConfig::get()->loadAppConfig('app');
        $gameSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME, GameConst::DEFAULT_GAME_SETTING_KEY, true);

        /**
         * @var ModuleProfileVo $profile
         */
        $profile = ModuleProfile::get()->createProfile($userId);
        $profileInfoList = new ModuleProfileInfoVoList(array());

        // 添加 profileInfo 数据
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_PLATFORM, $platformId, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_USER_LANG, $appConfigs['LANG'], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_TOWN_NAME, strtoupper(dechex($userId)) . '\'s Town', $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_CITIZENSHIP_ID, strval($gameSetting['initCountry']), $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_HEAD_IMG_ID, $platform == 'huawei' ? (int)rand(1,10) : $gameSetting['initIcon'], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_WATER, json_encode($this->getResourceConfig(GameConst::USER_INFO_TYPE_WATER, 'init')), $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_BLOCK, $gameSetting['initGroundBlockIds'], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_USER_PLATFORM, $platform, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_BUILDING_ORDER_COUNT, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_UPVOTE_SUM, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_UPVOTE_STAGE, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_UPVOTE_DAY, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_UPVOTE_DAY_TIME, $reqTime, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_UPVOTE_REWARD_ITEM_ID, -1, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_UPVOTE_REWARD_COUNT, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_UPVOTE_TOTAL_REWARD_COUNT, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_UPVOTED_USER_IDS, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_ORDER_COUNT, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_PLANT_POINT, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_MISSIONS, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_ACHIEVEMENT_SCORE, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_ACHIEVEMENT_REWARD_OBTAINED, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_COLLECTED_SERIES, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_ACHIEVEMENT_FINISHED, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_COC_TOKEN, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_COUPON_TOKEN, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_COLLECTION_TOKEN, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_GUIDE_INFO, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_GAME_ID, $appConfigs['GAME_ID'], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_PAY, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_REGISTRATION_IP, $ip, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_LAST_IP, $ip, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_PATCH_INFO, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_GIFT_CODE_BATC, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_IS_DONE_FIRST_ORDER, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_BUILDING_ORDER_REF_TIMES, [], $profileInfoList);

        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_TRADE_EXP_EACH_DAY, ['exp' => 0, 'updateTime' => 0], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_DAILY_SHARE_TIMES, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_DAILY_FRIEND_HANDLE_TIMES, [], $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_TURNTABLE_COIN, json_encode($this->getResourceConfig(GameConst::USER_INFO_TYPE_TURNTABLE_COIN, 'init')), $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_DAILY_GROUND_MONSTER, 0, $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_DAILY_GROUND_MONSTER_REFRESH, GameService::get()->getReqTime(), $profileInfoList);
        $this->createProfileInfo($userId, GameConst::USER_INFO_TYPE_REFRESH_TIME, 0, $profileInfoList);

        SzPersister::get()->setVoList($profileInfoList);
        return array($profile, $profileInfoList);
    }

    /**
     * 创建 profileInfoVo
     *
     * @param int $userId
     * @param int $infoType
     * @param int | array | string $info
     * @param ModuleProfileInfoVoList $profileInfoList default null
     * @return ModuleProfileInfoVo
     */
    private function createProfileInfo($userId, $infoType, $info, $profileInfoList = null)
    {
        if (!$profileInfoList) {
            $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        }

        // 当 info 是 number, 需要再加一层 json_encode
        if (!is_string($info) && !is_array($info)) {
            $info = json_encode($info);
        }

        $profileInfo = new ModuleProfileInfoVo(
            $userId,
            $infoType,
            json_encode($info),
            GameService::get()->getReqTime(),
            true
        );

        $profileInfoList->addElement($profileInfo);
        $profileInfoList->setElement($infoType, $profileInfo);

        return $profileInfo;
    }

    /**
     * Update user profile exp, also handle user level up case.
     *
     * @param int $userId
     * @param int $harvestExp
     * @param boolean $isMonthCard
     * @param ModuleProfileVo $profile default null
     * @return ModuleProfileVo
     */
    public function updateExp($userId, $harvestExp, $profile = null, $isMonthCard = true)
    {
        if ($isMonthCard) {
            /**
             * @var ModuleProfileInfoVo $profileInfo
             */
            $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
            $profileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_MONTH_CARD, false);
            if ($profileInfo) {
                $serverTime = GameService::get()->getReqTime();
                $reqTime = (int)$profileInfo->getRawInfo();
                if ($reqTime >= $serverTime) {
                    $expVip = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, 'expVip', true);
                    $expVip = $expVip['value'];
                    $harvestExp = (int)(($expVip + 100) / 100 * $harvestExp);
                }
            }
        }

        if (is_null($profile)) {
            $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        }

        if ($harvestExp == 0) {
            return $profile;
        }

        $oldLevel = $profile->getLevel();
        list(, $isLevelUp, $entityData) = $this->updateProfileExp($profile, $harvestExp, false, true);

        if ($isLevelUp) {
            GameService::get()->handleLevelUp($userId, $profile, $oldLevel);
        }

        if ($_SERVER['is_payment_callback'] ?? false) $profile->persist();
        SzPersister::get()->setVo($profile);

        // level up bonus exists, handle it
        if ($entityData && is_array($entityData)) {
            $tid = '';
            foreach ($entityData as $entityId => $count) {
                GameService::get()->handleEntity($userId, $entityId, $count, true, true);
                $tid .= $entityId . ':' . abs($count) . '|';
            }
            CommonFunctionService::get()->reportResourceUseDetails(99999988,$userId,1,$tid,'updateExp');//获得资源上报

            // 日志采集：玩家升级
            StatisticsService::get()->saveLog($profile->getUserId(), GameConst::LOG_TYPE_RESOURCE_OUTPUT, array(
                'clientData' => array(
                    'tid' => $tid,
                    'item' => 'Levelup',
                )
            ), $profile);
        }

        return $profile;
    }

    /**
     * Update user profile exp, also handle user level up case.
     *
     * @param ModuleProfileVo $profile
     * @param int $exp positive numbers
     * @param boolean $canUpgradeLastLevel default true
     * @param boolean $continuousMode default false
     * @return array '$profile => ModuleProfileVo, $isLevelUp => boolean, $items => array(itemId => count)'
     */
    public function updateProfileExp($profile, $exp, $canUpgradeLastLevel = true, $continuousMode = false)
    {
        $isLevelUp = false;
        $items = array(); // for bonus items when profile levelup

        if ($exp > 0) {
            $profile->setExp($profile->getExp() + $exp);
            list($profile, $isLevelUp) = $this->processUserExpUpdate($profile, $items, $canUpgradeLastLevel, $continuousMode);
        }

        return array($profile, $isLevelUp, $items);
    }

    /**
     * Process user exp update.
     *
     * @param ModuleProfileVo $profile
     * @param array $items passed as reference
     * @param boolean $canUpgradeLastLevel default true
     * <pre>
     *      When $canUpgradeLastLevel is false, update exp can not upgrade to the last level
     * </pre>
     * @param boolean $continuousMode default false
     * @return array(
     *      ModuleProfileVo $profile,
     *      boolean $isLevelUp
     * )
     */
    private function processUserExpUpdate($profile, &$items, $canUpgradeLastLevel = true, $continuousMode = false)
    {
        $isLevelUp = false;
        $levelUpBonusSettings = null;
        $expLadder = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELS, null, true);

        $originLevel = $profile->getLevel();
        $finalLevel = $originLevel;
        $exp = $profile->getExp();

        $this->calcExpUpdate($finalLevel, $exp, $expLadder, $continuousMode, true);

        // if can not upgrade last level, user exp will not add up exceed this level max exp
        if (!$canUpgradeLastLevel) {
            $ladderKeys = array_keys($expLadder);
            $lastLevel = array_pop($ladderKeys);
            if ($lastLevel == $finalLevel) {
                $finalLevel = $lastLevel - 1;
                $finalExp = $expLadder[$lastLevel]['exp'];
                if ($profile->getExp() > $finalExp) {
                    $profile->setExp($finalExp);
                }
            }
        }

        // there is some level up events
        if ($finalLevel != $originLevel) {
            $isLevelUp = true;
            // set updated data
            $profile->setLevel($finalLevel);
            $profile->setExp($exp);
            // handle level up bonus items
            if (is_null($levelUpBonusSettings)) {
                $levelUpBonusSettings = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELUP_BONUS, null, true);
            }

            //获取玩家平台数据
            $userId= $profile->getUserId();
            $platform = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_USER_PLATFORM);

        if ($platform == 'tt') {
                $items = SzUtility::handleLevelUpBonusTTBase($originLevel, $finalLevel, $levelUpBonusSettings);
            }else{
                $items = SzUtility::handleLevelUpBonus($originLevel, $finalLevel, $levelUpBonusSettings);
          }

            // 日志采集：玩家升级
            StatisticsService::get()->saveLog($profile->getUserId(), GameConst::LOG_TYPE_LEVEL_UP, array(), $profile);
        }

        return array($profile, $isLevelUp);
    }


    /**
     * Process user exp update.
     *
     * @param ModuleProfileVo $profile
     * @param array $items passed as reference
     * @param boolean $canUpgradeLastLevel default true
     * <pre>
     *      When $canUpgradeLastLevel is false, update exp can not upgrade to the last level
     * </pre>
     * @param boolean $continuousMode default false
     * @param   $platform
     * @return array(
     *      ModuleProfileVo $profile,
     *      boolean $isLevelUp
     * )
     */
    public function processTTUserExpUpdate($profile, &$items, $canUpgradeLastLevel = true, $continuousMode = false, $platform = 'tt')
    {
        $levelUpBonusSettings = null;
        $expLadder = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELS, null, true);

        $originLevel = $profile->getLevel();
        $finalLevel = $originLevel;

        // handle level up bonus items
        if (is_null($levelUpBonusSettings)) {
            $levelUpBonusSettings = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_LEVELUP_BONUS, null, true);
        }
        $entityData = SzUtility::handleLevelUpBonusTT($originLevel, $finalLevel, $levelUpBonusSettings,$platform);
        // level up bonus exists, handle it
        return $entityData;

    }

    /**
     * Calculate exp & level update.
     *
     * There are two mode of exp gaining:
     *     1. non-continuous mode: when level up, the exp value would be cleared to 0, and gain till next level again
     *     2. continuous mode: when level up, the exp value would not be cleared to 0
     *
     * <pre>
     * $expLadder = array(
     *     level => array(
     *         'level' => levelValue,
     *         'exp'   => expRequired
     *     ),
     *     ...
     * )
     * Actually the format of the $expLadder we need has only one rule, there have to be the key "exp".
     * The key "level" here is only the result of CCS output, we won't use it.
     *
     * $expLadder describe how much exp required to reach this config level:
     * e.g
     * $expLadder = array(
     *     2 => 100, // level up to 2 require exp 100
     *     3 => 250, // level up to 3 require exp 250
     * );
     * $level = 1; // current level is 1
     * $exp = 0; // current exp is 0
     *
     * when $exp was raised to 100, $level could be promoted to 2,
     * and $exp was reduced to 0 again.
     * </pre>
     *
     * @param int $level passed by reference
     * @param int $exp passed by reference
     * @param array $expLadder
     * @param boolean $continuousMode default false
     * @param boolean $allowLevelUpMultiTimes default false, is level up multi times at one exp calculation, default is false, means only one level can be gained at a time
     * @return void
     */
    public static function calcExpUpdate(&$level, &$exp, $expLadder, $continuousMode = false, $allowLevelUpMultiTimes = false)
    {
        $levelUp = false;
        $ladderExpValue = (SzUtility::checkArrayKey($level + 1, $expLadder)) ? $expLadder[$level + 1]['exp'] : false;
        if ($ladderExpValue // next level required exp exists, means did not reach the max level yet
            && $exp >= $ladderExpValue
        ) { // level up
            ++$level;
            if (!$continuousMode) { // non-continous mode, we need to reduce the remaining exp value
                $exp -= $ladderExpValue;
            }
            $levelUp = true;
        }
        if ($levelUp && $allowLevelUpMultiTimes) {
            // check can level up again or not
            $nextLadderExp = (SzUtility::checkArrayKey($level + 1, $expLadder)) ? $expLadder[$level + 1]['exp'] : false;
            if ($nextLadderExp && $exp >= $nextLadderExp) { // user not reach the max level, and still can levelup
                self::calcExpUpdate(
                    $level,
                    $exp,
                    $expLadder,
                    $continuousMode,
                    $allowLevelUpMultiTimes
                );
            }
        }
    }

    /**
     * Update user profile Token.
     *
     * @param int $userId
     * @param int $token 传入负值来扣除代币
     * @param ModuleProfileVo $profile default null
     * @param boolean $allowExceed default false
     * @param boolean $logCommodityConsume default true, 如果true，记为价值消耗
     * @param int $commodityValue default 0
     * <pre>
     * 当该参数为0
     *      1. token参数为正数，增加的token必定是免费代币，不需要处理价值流转
     *      2. token参数为负数，扣除的token可能为付费代币，根据付费代币数量的扣除量，扣除价值后，并在返回结果中加入扣除的价值，与扣除的付费代币量
     *      3. 判断$logCommodityConsume，如果为true，则记为价值消耗
     * 当该参数小于0
     *      1. 不可能出现的情况，将该参数调整为0
     * 当该参数大于0
     *      1. token参数为正数，增加的money必定为付费代币，并增加外部传入的价值
     *      2. token参数为负数，不可能出现的情况，如果出现，该参数调整为0
     *      3. 判断$logCommodityConsume，如果为true，则修改为false, 记为价值流入，并在外部进行日志采集
     * </pre>
     * @return array(ModuleProfileVo, int, array($entityId => array($count, $commodityValue), ...))
     */
    public function updateToken($userId, $token, $profile = null, $allowExceed = false, $logCommodityConsume = true, $commodityValue = 0)
    {
        if ($token > 0) {
            NewTaskService::get()->updateProgress($userId,GameConst::TASK_COIN,$token);
        }

        if ($token < 0) {
            TreeService::get()->updateProgress($userId,GameConst::TREE_TASK_USE_COIN,abs($token));
        }

        if (is_null($profile)) {
            $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        }

        if ($token == 0) {
            return array($profile, 0, array());
        }

        // 判断是否价值流入
        if ($commodityValue > 0 && $token > 0) {
            $logCommodityConsume = false;
        } else {
            $commodityValue = 0; // 不符合要求，重置为0
        }

        // 修改前的付费代币数量与价值
        $originPurchasedToken = $profile->getPurchasedToken();
        $originCommodityValue = $profile->getCommodityValue();

        ModuleProfile::get()->updateProfileToken($profile, intval($token), $allowExceed, $commodityValue);

        if ($_SERVER['is_payment_callback'] ?? false) $profile->persist();
        SzPersister::get()->setVo($profile);

        // 修改后的付费代币数量与价值
        $nowPurchasedToken = $profile->getPurchasedToken();
        $nowCommodityValue = $profile->getCommodityValue();

        // 判断当前属于价值流入还是价值流出
        $action = ($token > 0) ? GameConst::ENTITY_ACTION_GET : GameConst::ENTITY_ACTION_CONSUME;

        // 付费代币数量变更与价值变更
        $changePurchasedToken = $originPurchasedToken - $nowPurchasedToken;
        $changedCommodityValue = $originCommodityValue - $nowCommodityValue;

        if ($logCommodityConsume && $changedCommodityValue != 0) {
            // 任务进度计数：价值消耗
            StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_FINANCE_CASH, array(
                'clientData' => array(
                    'tid' => GameConst::ENTITY_ACTION_CONSUME_TOKEN . ':' . abs($changePurchasedToken) . '|',
                    'item' => 1,
                    'value' => abs($changedCommodityValue),
                )
            ));
        }

        // 拼接entityId
        $buildEntityId = EntityService::get()->buildEntityId($action, GameConst::ENTITY_TYPE_TOKEN, GameConst::ENTITY_PADDING_NULL);

        // 付费代币增加，价值为正数
        // 付费代币减少，价值为负数
        return array(
            $profile,
            $changedCommodityValue,
            array(
                $buildEntityId => array(
                    $changedCommodityValue,
                    $changePurchasedToken
                )
            )
        );
    }

    /**
     * 获取游戏币
     *
     * @param $userId
     * @param ModuleProfileVo $profile
     * @return int
     */
    public function getToken($userId, $profile = null)
    {
        if (is_null($profile)) {
            $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        }
        return $profile->getToken();
    }

    /**
     * Update numeric type user profile info value.
     *
     * @param int $userId
     * @param int $infoType
     * @param int | string | array $value 应该是int型，或array数组，该数值会被直接覆盖到 ModuleProfileInfoVo 的 info 字段中
     * @param ModuleProfileInfoVoList $profileInfoList
     * @param boolean $allowExceed default false
     * @param boolean $isToString default false
     * @throws SzException 20041
     * @return ModuleProfileInfoVoList
     */
    public function updateProfileInfo($userId, $infoType, $value, $profileInfoList = null, $allowExceed = false, $isToString = true)
    {
        $reqTime = GameService::get()->getReqTime();
        if (is_null($profileInfoList)) {
            $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        }
        $activityId = ActivityService::get()->getActivityTreeIndexId($userId);
        if ($infoType == GameConst::USER_INFO_TYPE_WATER && $value < 0 && $activityId) {
            ActivityService::get()->updateActivityTreeNum($userId,$activityId,abs($value));
        }

        $activityPersonalGoalsId = ActivityService::get()->getActivityPersonalGoalsIndexId($userId,$reqTime);
        if ($infoType == GameConst::USER_INFO_TYPE_WATER && $value < 0 && $activityPersonalGoalsId) {
            ActivityService::get()->updateActivityPersonalGoalsNum($userId,$activityPersonalGoalsId,abs($value),true);
        }

        /* @var ModuleProfileInfoVo $info */
        $info = $profileInfoList->getElement($infoType, false);
        if (!$info) {
            // 玩家数据扩展只有两种格式，1.array 2.int，所以这里要按传进来的value进行分别初始化
            $infoValue = (is_array($value)) ? array() : $this->getResourceConfig($infoType, 'init');
            $info = new ModuleProfileInfoVo($userId, $infoType, json_encode($infoValue), $reqTime, true);
        }

        if (!SzUtility::checkArrayKey($infoType, $this->infoResourceNameMap)) {
            // 当传入的是数字，强制转成 string
            if (!is_string($value) && !is_array($value)) {
                $value = json_encode($value);
            }
            // 非资源类用户信息数据，直接更新
            $info->setInfo($value);
            $info->setTime($reqTime);
        } else {
            // 资源类用户信息数据，根据配置计算回复量
            $limit = $this->getResourceConfig($infoType, 'limit');
            if ($infoType == GameConst::USER_INFO_TYPE_WATER) {
                /**
                 * @var ModuleProfileInfoVo $profileInfo
                 */
                $profileInfo = $profileInfoList->getElement(GameConst::USER_INFO_TYPE_MONTH_CARD, false);
                if ($profileInfo) {
                    $serverTime = GameService::get()->getReqTime();
                    $reqTime1 = (int)$profileInfo->getRawInfo();
                    if ($reqTime1 >= $serverTime) {
                        $waterLimitVip = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, 'waterLimitVip', true);
                        $limit = $waterLimitVip['value'];
                    }
                }
            }

            $recoveryStartTime = $this->getResourceConfig($infoType, 'chargeStartTime');

            // 可恢复类资源数据处理
            if (SzUtility::checkArrayKey($infoType, $this->infoRefreshResourcePossibleConfigKeys)) {
                $info = $this->refreshProfileInfo($userId, $infoType, $info, $limit);

                // 已达上限，不允许溢出
                if ($allowExceed && $info->getRawInfo() >= $limit) {
                    $allowExceed = false;
                }

                // 转盘代币
                if ($infoType == GameConst::USER_INFO_TYPE_TURNTABLE_COIN ) {
                    $allowExceed = true;
                }
            }

            // 更新资源数值
            $originValue = $info->getRawInfo();
            if ($value < 0) {
                // 消耗资源，可以继续超过上限, 不需要判断上限
                $allowExceed = true;
            } else {
                // 不可超过资源上限类，并且不需要报错
                if (SzUtility::checkArrayKey($infoType, $this->infoResourceNotAllowExceedKeys)) {
                    if ($infoType == GameConst::USER_INFO_TYPE_WATER) {
                        $allowExceed = true;
                    } else {
                        $allowExceed = false;
                    }
                } else {
                    // 获得资源，假设资源已满的情况下，则系统报错
                    if (!$allowExceed && $originValue >= $limit) {
                        if ($info->isInsert() && !SzUtility::checkArrayKey($infoType, $profileInfoList->getList())) {
                            $profileInfoList->addElement($info);
                            $profileInfoList->setElement($infoType, $info);
                        } else {
                            $profileInfoList->updateElement($infoType, $info);
                        }
                        return $profileInfoList;
                    }
                }
            }

            if ($allowExceed) {
                $changedValue = $originValue + $value;
            } else {
                $changedValue = min($originValue + $value, $limit);
            }

            if ($changedValue < 0 && $value < 0) {
                throw new SzException(20041, $infoType); // not enough resource
            }

            if ($originValue != $changedValue) {
                // 更新资源
                if ($isToString) {
                    $info->setInfo((string)$changedValue);
                } else {
                    $info->setInfo($changedValue);
                }

                // 当原资源数量到达上限，经过使用，资源最后未达到上限，则将当前时间设定为最后更新时间
                if (
                    $recoveryStartTime == -1
                    && $originValue >= $limit
                    && $changedValue < $limit
                ) {
                    $info->setTime($reqTime);
                }
            }
        }

        if ($_SERVER['is_payment_callback'] ?? false) $info->persist();
        if ($info->isInsert() && !SzUtility::checkArrayKey($infoType, $profileInfoList->getList())) {
            $profileInfoList->addElement($info);
            $profileInfoList->setElement($infoType, $info);
        } else {
            $profileInfoList->updateElement($infoType, $info);
        }

        SzPersister::get()->setVoList($profileInfoList);

        return $profileInfoList;
    }

    /**
     * Get resource user info config value.
     *
     * <pre>
     * </pre>
     *
     * @see GameConst::USER_INFO_RESOURCE_KEY_MAP
     * @param int $infoType
     * @param string $configKey possible config key are all defined in GameConst::USER_INFO_RESOURCE_KEY_MAP
     * @return mixed $configValue
     */
    public function getResourceConfig($infoType, $configKey)
    {
        $configValue = null;

        // 验证传入的用户扩展信息类型是否可能有配置
        if (!SzUtility::checkArrayKey($infoType, $this->infoResourceNameMap)) {
            return $configValue;
        }

        // 验证传入的配置键，是否合法
        if (!in_array($configKey, $this->infoResourcePossibleConfigKeys)) {
            return $configValue;
        }

        $configKey = $this->infoResourceNameMap[$infoType] . ucfirst($configKey);

        // FIXME 等待前端修复后删除
        if ($infoType == GameConst::USER_INFO_TYPE_AIRPLANE_TICKET && $configKey == 'init') {
            return 0;
        }

        $config = SzConfig::get()->loadAppConfig(ModuleProfileConst::CONFIG_PROFILE, $configKey, true); // array('key' => ..., 'value' => ...)

        return intval($config['value']);
    }

    /**
     * 刷新 user profileInfo 中的可自动恢复的玩家数据
     * Refresh profile info resource according to now time & last info resource charged time.
     *
     * @param int $userId
     * @param int $infoType
     * @param ModuleProfileInfoVo $profileInfo
     * @param int $limit
     * @return ModuleProfileInfoVo $profileInfo
     */
    public function refreshProfileInfo($userId, $infoType, $profileInfo, $limit = null)
    {
        $now = GameService::get()->getReqTime();

        if (!$profileInfo) {
            $profileInfo = new ModuleProfileInfoVo(
                $userId,
                $infoType,
                json_encode($this->getResourceConfig($infoType, 'init')),
                $now,
                true
            );
        }

        $originValue = $profileInfo->getRawInfo();
        $infoType = $profileInfo->getInfoType();
        if (is_null($limit)) {
            $limit = $this->getResourceConfig($infoType, 'limit');
        }
        $recoveryInterval = $this->getResourceConfig($infoType, 'chargeInterval');
        $recoveryAmount = $this->getResourceConfig($infoType, 'chargeAmount');
        $recoveryStartTime = $this->getResourceConfig($infoType, 'chargeStartTime');

        // 步长值设置未 0, 则认为不属于可恢复数据
        if ($recoveryInterval == 0) {
            $changedValue = $originValue;
            $lastChargedTime = $profileInfo->getTime();
        } else {
            list($changedValue, $lastChargedTime) = SzUtility::refreshRegularlyGeneratedResourceCount(
                $originValue,
                ($recoveryStartTime == -1) ? $profileInfo->getTime() : SzUtility::getCurrentCycleCoolDownTime($profileInfo->getTime(), $recoveryStartTime, $recoveryInterval),
                $limit,
                $recoveryInterval,
                $recoveryAmount,
                ($recoveryStartTime == -1) ? $now : SzUtility::getCurrentCycleCoolDownTime($now, $recoveryStartTime, $recoveryInterval)
            );
        }

        if ($originValue != $changedValue) {
            $profileInfo->setInfo($changedValue);
            $profileInfo->setTime($lastChargedTime);
        } else {
            $profileInfo->setTime($lastChargedTime);
        }

        return $profileInfo;
    }

    /**
     * Get numeric type user profile info value.
     *
     * @param int $userId
     * @param int $infoType
     * @param ModuleProfileInfoVoList $profileInfoList default null
     * @return null | array | int
     */
    public function getInfo($userId, $infoType, $profileInfoList = null)
    {
        if (is_null($profileInfoList)) {
            $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        }

        /* @var ModuleProfileInfoVo $info */
        $info = $profileInfoList->getElement($infoType, false);

        if ($infoType == GameConst::USER_INFO_TYPE_USER_PLATFORM and is_null($info)) {
            return PlatformBase::PLATFORM_TT;
        }

        if ($info) {
            return $info->getRawInfo();
        } else {
            return null;
        }
    }

    /**
     * 获取玩家等级
     *
     * @param int $userId
     * @return int $userLevel
     */
    public function getUserLevel($userId)
    {
        /** @var ModuleProfileVo $profile */
        $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        if (!$profile) {
            return 0;
        }

        return $profile->getLevel();
    }

    /**
     * 获取玩家经验
     *
     * @param int $userId
     * @return int $userLevel
     */
    public function getUserExp($userId)
    {
        /** @var ModuleProfileVo $profile */
        $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        if (!$profile) {
            return 0;
        }

        return $profile->getExp();
    }

    /**
     * 获取玩家最后登录时间
     *
     * @param int $userId
     * @return int $userLevel
     */
    public function getUserLastLoginTime($userId)
    {
        /** @var ModuleProfileVo $profile */
        $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        if (!$profile) {
            return 0;
        }

        return $profile->getLastLoginTime();
    }

    /**
     * 获取玩家注册时间
     *
     * @param int $userId
     * @return int $userLevel
     */
    public function getUserRegisterTime($userId)
    {
        /** @var ModuleProfileVo $profile */
        $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
        if (!$profile) {
            return 0;
        }

        return $profile->getRegisterTime();
    }

    /**
     * 更新玩家小镇名
     *
     * @param int $userId
     * @param string $townName
     * @throws SzException
     */
    public function updateTownName($userId, $townName)
    {
        $townName = urldecode($townName);
        $townName = trim($townName);
        $townName = GameService::get()->delEmjoy($townName);
        $tmpName = strtolower($townName);
        $appConfig = SzConfig::get()->loadAppConfig("app");
        $gameSetting = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME, GameConst::DEFAULT_GAME_SETTING_KEY, true);
        if (strlen($townName) <= 0 || strlen($townName) > $gameSetting['playerNameBit']) {
            throw new SzException(20053);
        }

        $banedTextNameSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_BANEDTEXT_NAME, null, true);
        foreach ($banedTextNameSettings as $_banedTextNameSetting) {
            if ($appConfig['IS_NEED_REPLACE']) {
                if (false === strpos(preg_replace('# #', '', $tmpName), strtolower($_banedTextNameSetting['banedList']))) {
                    continue;
                }
            } else {
//                if (false === strpos($tmpName, strtolower($_banedTextSetting['banedList']))) {
                if (strtolower($tmpName) != strtolower($_banedTextNameSetting['banedList'])) {
                    continue;
                }
            }
            throw new SzException(20069);
        }

        // 更新店名
        $this->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_TOWN_NAME, $townName);

        // 数据采集：更换店名
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:user|action:change_town_name|',
                'item' => $townName,
                'value' => 1
            )
        ));
    }

    /**
     * 获取花镇名称
     *
     * @param $userId
     * @param ModuleProfileInfoVoList $profileInfoList default null
     * @return string
     */
    public function getTownName($userId, $profileInfoList = null)
    {
        $townName = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_TOWN_NAME, $profileInfoList);
//        return GameService::get()->utf82unicode($townName);
//        return json_encode($townName, true);
        return json_encode((!$townName) ? '' : $townName, JSON_UNESCAPED_UNICODE);
    }

    /**
     * 更新玩家头像
     *
     * @param int $userId
     * @param int $headIconId
     * @throws SzException
     */
    public function updateHeadIcon($userId, $headIconId)
    {
        $playerIconSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_PLAYER_ICON, null, true);
        if (!SzUtility::checkArrayKey($headIconId, $playerIconSettings)) {
            throw new SzException(20055);
        }

        // 更新头像
        $this->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_HEAD_IMG_ID, $headIconId);

        // 数据采集: 玩家更换头像
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:user|action:change_avatar|',
                'item' => $headIconId,
                'value' => 1
            )
        ));
    }

    /**
     * 获取玩家头像
     *
     * @param int $userId
     * @param ModuleProfileInfoVoList $profileInfoList default null
     * @return string
     */
    public function getHeadIcon($userId, $profileInfoList = null)
    {
        $icon = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_HEAD_IMG_ID, $profileInfoList);
        return $icon ? $icon : 0;
    }

    /**
     * 更新玩家城市
     *
     * @param int $userId
     * @param string $citizenshipId
     * @throws SzException
     */
    public function updateCitizenship($userId, $citizenshipId)
    {
        $citizenshipSettings = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_PLAYER_COUNTRY, null, true);
        if (!SzUtility::checkArrayKey($citizenshipId, $citizenshipSettings)) {
            throw new SzException(20054);
        }

        // 更新城市
        $this->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_CITIZENSHIP_ID, $citizenshipId);
    }

    /**
     * 更新语种
     *
     * @param int $userId
     * @param int $language
     * @throws SzException
     * @return boolean
     */
    public function updateLanguage($userId, $language)
    {
        switch ($language) {
            case 1:
                $lang = GameConst::LANGUAGE_EN;
                break;
            case 2:
                $lang = GameConst::LANGUAGE_CN;
                break;
            default:
                $lang = $this->getLanguageById($language);
        }

        $this->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_USER_LANG, $lang);

        // 数据采集：更换语种
        StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_ORBIT, array(
            'clientData' => array(
                'tid' => 'Module:user|action:change_language|',
                'item' => $lang,
                'value' => 1
            )
        ));

        return true;
    }

    /**
     * 获取语言字符串
     * @param $Id
     * @return string
     */
    public function getLanguageById($Id)
    {
        $languageConfig = SzConfig::get()->loadAppConfig(GameConst::CONFIG_GAME_LANGUAGE, $Id, true);
        $formArr = explode('.', $languageConfig['form']); //FlowerTown.Config.Ft_enConfig => ['FlowerTown', 'Config', 'Ft_enConfig']
        return strtolower(substr($formArr[2], 0, strlen($formArr[2]) - 6)); //Ft_enConfig => Ft_en => ft_en
    }

    /**
     * 获取平台id
     *
     * @param int $userId
     * @param string $platform
     * @return mixed|string
     */
    public function getPlatformId($userId, $platform)
    {
        $platforms = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_PLATFORM_INFO);
        if (!$platforms || !SzUtility::checkArrayKey($platform, $platforms)) {
            return '';
        }
        return str_replace($platform . '_', '', $platforms[$platform]);
    }

    /**
     * 当前登录平台token
     *
     * @param int $userId
     * @return mixed|string
     */
    public function getPlatformToken($userId)
    {
        return $this->getInfo($userId, GameConst::USER_INFO_TYPE_USER_PLATFORM_TOKEN);
    }

    /**
     * 更新账号对应的平台信息
     * @param int $userId
     * @param array $platformInfo array($platform => $platformId, ...)
     * @return void
     * @throws SzException
     */
    public function updatePlatformInfo($userId, $platformInfo)
    {
        //获取老平台信息
        $oldPlatformInfo = $this->getInfo($userId, GameConst::USER_INFO_TYPE_PLATFORM_INFO);
        $oldPlatformInfo = is_null($oldPlatformInfo) ? [] : $oldPlatformInfo;

        $update = false;
        foreach ($platformInfo as $platform => $platformId) {
            if (!isset($oldPlatformInfo[$platform]) || $oldPlatformInfo[$platform] != $platformId) {
                $oldPlatformInfo[$platform] = $platformId;
                $update = true;
            }
        }

        $update && $this->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_PLATFORM_INFO, $oldPlatformInfo);
    }

    /**
     * 初始化用户平台信息(微信名字，头像)
     *
     * @param int $userId
     * @param array $platformInfo
     * @return boolean
     */
    public function platformInfoInit($userId, $platformInfo)
    {
        $nickName = isset($platformInfo['nickName']) ? $platformInfo['nickName'] : '';
        $avatarUrl = isset($platformInfo['avatarUrl']) ? $platformInfo['avatarUrl'] : '';
        $profileInfoList = SzPersister::get()->getVoList($userId, ModuleProfileConst::ORM_NAME_PROFILE_INFO);
        if ($nickName) {
            $nickName = GameService::get()->delEmjoy($nickName);
            UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_TOWN_NAME, $nickName, $profileInfoList);
        }
        if ($avatarUrl) {
            UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_HEAD_IMG_ID, $avatarUrl, $profileInfoList);
        }
        if (isset($platformInfo['gender'])) {
            UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_GENDER, $platformInfo['gender'], $profileInfoList);
        }
        if ($nickName && $avatarUrl) {
            UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_USER_PLATFORM_INFO, 1, $profileInfoList);
            // 记录日志：新安装用户采集
            StatisticsService::get()->saveLog($userId, GameConst::LOG_TYPE_USER_INFO, array(
                'clientData' => array(
                    'tid' => "name:{$nickName}|gdr:{$platformInfo['gender']}|loc:|uname:{$nickName}|tz:|em:|",
                )
            ));
        }
        return true;
    }

    /**
     * 获取玩家性别
     * @param int $userId
     * @param ModuleProfileInfoVoList $profileInfoList default null
     * @return int
     */
    public function getGender($userId, $profileInfoList = null)
    {
        $gender = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_GENDER, $profileInfoList);
        return is_null($gender) ? 0 : $gender;
    }

    /**
     * @param $userId
     * @param null $profileInfoList
     * @return array|int|null
     */
    public function getFlowerLevel($userId, $profileInfoList = null) {
        $flowerLevel = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_FLOWER_LEVEL, $profileInfoList);
        return is_null($flowerLevel) ? 0 : $flowerLevel;
    }

    /**
     * @param $userId
     * @param null $profileInfoList
     * @return array|int|null
     */
    public function getFlowerLevel1($userId, $profileInfoList = null) {
        $flowerLevel = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_FLOWER_LEVEL, $profileInfoList);
        $flowerLevelExpireTime = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_FLOWER_LEVEL_EXPIRE_TIME, $profileInfoList);
        $flowerLevelExpireTime = is_null($flowerLevelExpireTime) ? 0 : $flowerLevelExpireTime;
        if (GameService::get()->getReqTime() > $flowerLevelExpireTime) {
            return 0;
        } else {
            return is_null($flowerLevel) ? 0 : $flowerLevel;
        }
    }

    /**
     * @param $userId
     * @param null $profileInfoList
     * @return array|int|null
     */
    public function getFlowerLevelExpireTime($userId, $profileInfoList = null) {
        $flowerLevelExpireTime = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_FLOWER_LEVEL_EXPIRE_TIME, $profileInfoList);
        return is_null($flowerLevelExpireTime) ? 0 : $flowerLevelExpireTime;
    }

    /**
     * 获取月卡信息
     *
     * @param int $userId
     * @param ModuleProfileInfoVoList $profileInfoList default null
     * @return string
     */
    public function getMonthCard($userId, $profileInfoList = null)
    {
        $expireTime = UserService::get()->getInfo($userId, GameConst::USER_INFO_TYPE_MONTH_CARD, $profileInfoList);
        return $expireTime ? $expireTime : 0;
    }

    /**
     * 创建机器人用户
     *
     * @param $limit
     * @return string[]
     * @throws SzException
     */
    public function createRobotsUser($limit): array
    {
        // 第一个库名称
        $dbNames = SzConfig::get()->loadAppConfig('database', 'MySql');
        $dbName = $dbNames[0]['DB_NAME'];

        // 获取还没创建的机器人用户
        $robotsList = SzContextFactory::get()->getDb()->select("select a.* from {$dbName}.ft_init_robots as a left join {$dbName}.module_sns as b on a.platformId=b.platformId where b.platformId is null and a.id >= 350 order by a.id asc limit {$limit}");

        if (empty($robotsList)) {
            return [
                'message' => '无可新增用户'
            ];
        }

        try {
            $platform = PlatformBase::PLATFORM_TT;
            foreach ($robotsList as $item) {
                // 注册用户
                $platformId = $item['platformId'];
                $platformInfo = [$platform => $platformId];
                list($userId, $platformInfo) = PlatformService::get()->portPlayerUserId($platformInfo, true);

                // 获取玩家数据，如果玩家不存在，则创建
                $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
                if (!$profile) {
                    GameService::get()->createPlayerGameData($userId, $platformId, $platform, $platformInfo);
                    $profile = SzPersister::get()->getVo($userId, ModuleProfileConst::ORM_NAME_PROFILE);
                }

                // 更新等级
                $profile->setLevel($item['level']);
                SzPersister::get()->setVo($profile);

                // 清空网络序号包
                SzContextFactory::get()->getStaticCache($userId)->delete(GameConst::CACHE_KEY_PKG_INDEX . $userId);

                // 更新用户头像等信息
                UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_GENDER, $item['gender']);
                UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_TOWN_NAME, $item['townName']);
                UserService::get()->updateProfileInfo($userId, GameConst::USER_INFO_TYPE_HEAD_IMG_ID, $item['headImgId']);
                sleep(1);
            }

            return [
                'message' => '用户创建完成 ' . count($robotsList) . ' 个.'
            ];
        } catch (\Exception $exception) {
            if (empty($robotsList)) {
                return [
                    'message' => $exception->getMessage(),
                    'file' => $exception->getFile(),
                    'line' => $exception->getLine(),
                ];
            }
        }
    }
}
